/****************************************************************************

 Copyright (C) 2009 Jean Pierre Charalambos. All rights reserved.

 Jean Pierre Charalambos (http://dis.unal.edu.co/profesores/pierre/) is an
 Affiliate Associate Professor of the Faculty of Engineering at National
 University of Colombia (http://www.unal.edu.co/).

 This file is part of maku version 0.1.

 http://code.google.com/p/maku/
 email: jpcharalambosh@unal.edu.co

 This file may be used under the terms of the GNU General Public License 
 versions 2.0 or 3.0 as published by the Free Software Foundation and
 appearing in the COPYING file included in the packaging of this file.

 This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

*****************************************************************************/

#ifndef SIMPLIFIERT_H
#define SIMPLIFIERT_H

#include "decimateroptions.h"

#include <OpenMesh/Core/IO/MeshIO.hh>
#include <OpenMesh/Tools/Utils/Timer.hh>
#include <OpenMesh/Tools/Decimater/DecimaterT.hh>
#include <OpenMesh/Tools/Decimater/ModNormalFlippingT.hh>
#include <OpenMesh/Tools/Decimater/ModQuadricT.hh>
#include <OpenMesh/Tools/Decimater/ModProgMeshT.hh>
#include <OpenMesh/Tools/Decimater/ModIndependentSetsT.hh>
#include <OpenMesh/Tools/Decimater/ModRoundnessT.hh>

//-------------------- namespaces ----

using namespace OpenMesh;

template <typename M>
class SimplifierT {
public:
  typedef M                                                                Mesh;
  typedef Decimater::DecimaterT< Mesh >                           DecimaterType;
  typedef typename Decimater::CollapseInfoT< Mesh >                CollapseInfo;
  typedef typename Decimater::ModQuadricT<DecimaterType>::Handle           ModQ;
  typedef typename Decimater::ModNormalFlippingT<DecimaterType>::Handle   ModNF;
  typedef typename Decimater::ModProgMeshT<DecimaterType>::Handle         ModPM;
  typedef typename Decimater::ModIndependentSetsT<DecimaterType>::Handle  ModIS;
  typedef typename Decimater::ModRoundnessT<DecimaterType>::Handle         ModR;

  static bool simplify( Mesh & mesh, DecimaterOptions * decOpt ) {
    using namespace std;
    OpenMesh::Utils::Timer timer;
    //instanciate modules:
    ModQ  modQ;
    ModNF modNF;
    ModPM modPM;
    ModIS modIS;
    ModR  modR;

    // ---- 0 - For module NormalFlipping one needs face normals

    //if ( !opt.check( OpenMesh::IO::Options::FaceNormal ) ) {
    if ( !mesh.has_face_normals() )
      mesh.request_face_normals();

    clog << "  updating face normals" << endl;
    mesh.update_face_normals();

    // ---- 1 - create decimater instance
    DecimaterType decimater(mesh);

    // ---- 2 - registrate modules
    clog << "  registrate modules" << endl;

    decimater.add(modQ);

    if (decOpt->hasConstraintQ)
      decimater.module( modQ ).set_max_err( decOpt->Q );

    if (decOpt->hasNF) {
      decimater.add(modNF);
      if (decOpt->hasNF)
	decimater.module( modNF ).set_normal_deviation( decOpt->NF );
    }

    if ( decOpt->hasIS )
      decimater.add(modIS);

    if ( decOpt->hasR ) {
      decimater.add( modR );
      //if ( _opt.R.has_value() )
	decimater.module( modR ).set_min_angle( decOpt->R, !modQ.is_valid() || !decimater.module(modQ).is_binary());
    }

    // ---- 3 - initialize decimater

    clog << "initializing mesh" << endl;

    bool initDecimater;
    timer.start();
    initDecimater = decimater.initialize();
    timer.stop();
    if (!initDecimater) {
      cerr << "  initializing failed!" << endl;
      return false;
    }

    clog << "  Elapsed time: " << timer.as_string() << endl;
    decimater.info( clog );

    // ---- 4 - do it

    clog << "decimating" << endl;
    clog << "  # vertices: "       << mesh.n_vertices() << endl;

    float nv_before = float(mesh.n_vertices());

    timer.start();
    int rc = 0;

    if ( decOpt->targetIsPercentage )
      rc = decimater.decimate_to( size_t( (decOpt->maxVertices*decOpt->nPercentage)/100.00) );
    else if ( decOpt->targetIsVertices )
      rc = decimater.decimate_to( size_t(decOpt->nVertices) );
    else
      rc = decimater.decimate( size_t(decOpt->nCollapses) );

    timer.stop();

    /**
    // ---- 5 - write progmesh file for progviewer (before garbage collection!)

    if ( _opt.PM.has_value() )
      decimater.module(modPM).write( _opt.PM );
    // */
    // ---- 6 - throw away all tagged edges

    mesh.garbage_collection();

    clog << "  # executed collapses: " << rc << endl;
    clog << "  # vertices: " << mesh.n_vertices() << ", " 
         << ( 100.0*mesh.n_vertices()/nv_before ) << "%\n";
    clog << "  Elapsed time: " << timer.as_string() << endl;
    clog << "  collapses/s : " << rc/timer.seconds() << endl;

    return true;
  }

};

#endif
